/*
 *    SimpleIncrementalFrequentItemsetLearner.java
 *    Copyright (C) 2010 University of Waikato, Hamilton, New Zealand
 *    @author Albert Bifet (abifet@cs.waikato.ac.nz)
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package moa.learners;

import java.util.ArrayList; 
import java.util.Collections; 
import java.util.HashMap; 
import java.util.List;
import java.util.StringTokenizer;

import moa.MOAObject;
import moa.core.DoubleVector;
import moa.core.Measurement;
import moa.core.StringUtils;
import moa.learners.prefix.AlgoPrefixSpan;
import moa.learners.prefix.SequenceDatabase;
import moa.options.FloatOption;
import moa.options.IntOption;
import weka.core.Instance;
import moa.learners.prefix.SequenceDatabase;

public class  SimpleIncrementalFrequentSequenceLearner extends AbstractLearner {
	private static final long serialVersionUID = 1L;
	
	private SequenceDatabase sequenceDatabase;
	
	private AlgoPrefixSpan algo;
	
	public IntOption windowSizeOption = new IntOption(
			"windowSize", 'w',
			"Size of the sliding window.", 5000);
			
	public IntOption batchSizeOption = new IntOption(
			"batchSize", 'b',
			"Size of a batch of examples.", 1000);
	
	public FloatOption minSupportOption = new FloatOption(
			"minSupport", 's',
			"Minimum support.", 0.1, 0, 1);
			
	@SuppressWarnings("hiding")
	public static final String classifierPurposeString = "Simple Incremental Frequent Itemset Learner.";

	protected ArrayList<HashMap<List<List<Integer>>, Integer>> window;
	protected HashMap<List<List<Integer>>, Integer> frequentSequences;
	protected ArrayList<List<List<Integer>>> batchSequences;
	protected int currentWindowBatch;

	@Override
	public void resetLearningImpl() {
	    // inits a window of batches W
        this.window = new ArrayList<HashMap<List<List<Integer>>, Integer>>();

        // inits a list of frequent sequence storing sequences and supports
    	this.frequentSequences = new HashMap<List<List<Integer>>, Integer>();
    	// inits batch 
		this.batchSequences = new ArrayList<List<List<Integer>>>();
		this.currentWindowBatch = 0;
	}
	
	public List<List<Integer>> sequenceInstance(Instance inst){
		List<List<Integer>> sequence = new ArrayList<List<Integer>>();
		String stringInstance = inst.toString();
		stringInstance = stringInstance.substring(1, stringInstance.length()-1);
		StringTokenizer st = new StringTokenizer(stringInstance);
		List<Integer> itemset = new ArrayList<Integer>();
		while (st.hasMoreTokens()) {
			String val = st.nextToken();
			if (val.equals("-1"))
			{	
				sequence.add(itemset);
				itemset = new ArrayList<Integer>();
			}
			else
			{	
				itemset.add(Integer.parseInt(val));	
			}		
		}
		return sequence;
	}
	
	public String toString(List<List<Integer>> sequence){
		StringBuffer r = new StringBuffer ();
		for(List<Integer> attribute : sequence){
			for(int itemSet : attribute){
				r.append(itemSet);
				r.append(' ');
			}
			r.append('-');
		}
		return r.toString();
	}

	protected HashMap<List<List<Integer>>, Integer> BatchMiner(ArrayList<List<List<Integer>>> batchSequences){
		// TO DO: Replace by a frequent sequence learner
		HashMap<List<List<Integer>>, Integer> batchFrequentSequences = new HashMap<List<List<Integer>>, Integer>();
		sequenceDatabase = new SequenceDatabase(); 
		for ( List<List<Integer>> is : batchSequences){
			sequenceDatabase.addSequenceBatch(is);
		}
		// Create an instance of the algorithm the good support
		algo = new AlgoPrefixSpan(minSupportOption.getValue()); 
		algo.runAlgorithm(sequenceDatabase);
		batchFrequentSequences = algo.getStatistics(sequenceDatabase.size());
		double minSupport =  this.minSupportOption.getValue()* this.batchSizeOption.getValue();
		System.out.println("Batch miner --");
		//batchFrequentSequences.toString();
		deleteInfrequentSequences(batchFrequentSequences, minSupport);
		System.out.println("Batch size: "+batchFrequentSequences.size());
		return batchFrequentSequences;
	}

    protected void deleteInfrequentSequences(HashMap<List<List<Integer>>, Integer> batchFrequentSequences, double minSupport) {
		ArrayList<List<List<Integer>>> sequenceDelete = new ArrayList<List<List<Integer>>>();
		for ( List<List<Integer>> is : batchFrequentSequences.keySet()){
			// remove infrequent patterns
			for (List<Integer> it : is)
				if (batchFrequentSequences.get(is) < minSupport){
					sequenceDelete.add(is);
				} 
		 }
		 for (List<List<Integer>> is :  sequenceDelete){
			 batchFrequentSequences.remove(is);
		 }
	 }

	@Override
	public void trainOnInstanceImpl(Instance inst) {
		// every new sequence is stored in a batch 
		List<List<Integer>> sequence = sequenceInstance(inst);
		this.batchSequences.add(sequence);

		// when the current batch is full
		if ( this.batchSequences.size() == this.batchSizeOption.getValue()) {
			// compute the frequent sequences using a batch method and store them in the window
			HashMap<List<List<Integer>>, Integer> batchfrequentSequences = BatchMiner(this.batchSequences); 
			batchfrequentSequences.toString();
			this.window.add(batchfrequentSequences);
			
    		// add these new frequent sequences, updating support counts
    		for ( List<List<Integer>> is : batchfrequentSequences.keySet()){
				 if (this.frequentSequences.get(is) != null) {
				 	this.frequentSequences.put(is, this.frequentSequences.get(is) + batchfrequentSequences.get(is));
				 } else {
					this.frequentSequences.put(is, batchfrequentSequences.get(is)); 
				 }
			}
       		// if the window is full remove data from oldest batch
       		if (this.currentWindowBatch * this.batchSizeOption.getValue() >= this.windowSizeOption.getValue()){
				//delete the old frequent patterns from the oldest batch in W, updating support counts
				for ( List<List<Integer>> is : this.window.get(0).keySet()){
					if (this.frequentSequences.get(is)!= null){
						this.frequentSequences.put(is, this.frequentSequences.get(is) - this.window.get(0).get(is));
					} else {
						this.frequentSequences.put(is, 0);
					}
				}
				this.window.remove(0);
			}
			double minSupport = this.minSupportOption.getValue() * this.window.size() * this.batchSizeOption.getValue();
			deleteInfrequentSequences(this.frequentSequences, minSupport);
			
   			// start a new batch	
			this.batchSequences = new ArrayList<List<List<Integer>>>();
			this.currentWindowBatch++;
		}
	}

	@Override
	protected Measurement[] getModelMeasurementsImpl() {
		return null;
	}

	@Override
	public void getModelDescription(StringBuilder out, int indent) {
		// Output frequent frequences
		StringUtils.appendIndented(out, indent, "Number of frequent itemsets: ");
		out.append(this.frequentSequences.size());
		StringUtils.appendNewline(out);
		for ( List<List<Integer>> is : this.frequentSequences.keySet()){
			StringUtils.appendIndented(out, indent, "Support of <");
			for (List<Integer> i : is){
				out.append("( ");
				for (Integer j : i)
				{
					out.append(" ");
					out.append(j);
				}	
				out.append(") ");
			}
			out.append(" >: ");
			out.append(this.frequentSequences.get(is));
			StringUtils.appendNewline(out);
		}
	}

	@Override
	public MOAObject getModel() {
		return null;
	}

	public boolean isRandomizable() {
		return false;
	}

}
